3 * Options for the PHP parser
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
24 use MediaWiki\MediaWikiServices
;
25 use Wikimedia\ScopedCallback
;
28 * @brief Set options of the Parser
30 * How to add an option in core:
31 * 1. Add it to one of the arrays in ParserOptions::setDefaults()
32 * 2. If necessary, add an entry to ParserOptions::$inCacheKey
33 * 3. Add a getter and setter in the section for that.
35 * How to add an option in an extension:
36 * 1. Use the 'ParserOptionsRegister' hook to register it.
37 * 2. Where necessary, use $popt->getOption() and $popt->setOption()
45 * Default values for all options that are relevant for caching.
46 * @see self::getDefaults()
49 private static $defaults = null;
55 private static $lazyOptions = [
56 'dateformat' => [ __CLASS__
, 'initDateFormat' ],
60 * Specify options that are included in the cache key
63 private static $inCacheKey = [
65 'numberheadings' => true,
67 'stubthreshold' => true,
73 * Current values for all options that are relevant for caching.
79 * Timestamp used for {{CURRENTDAY}} etc.
81 * @note Caching based on parse time is handled externally
88 * @todo Track this for caching somehow without fragmenting the cache insanely
93 * Function to be called when an option is accessed.
95 * @note Used for collecting used options, does not affect caching
97 private $onAccessCallback = null;
100 * If the page being parsed is a redirect, this should hold the redirect
103 * @todo Track this for caching somehow
105 private $redirectTarget = null;
108 * Appended to the options hash
110 private $mExtraKey = '';
113 * @name Option accessors
118 * Fetch an option, generically
120 * @param string $name Option name
123 public function getOption( $name ) {
124 if ( !array_key_exists( $name, $this->options
) ) {
125 throw new InvalidArgumentException( "Unknown parser option $name" );
128 if ( isset( self
::$lazyOptions[$name] ) && $this->options
[$name] === null ) {
129 $this->options
[$name] = call_user_func( self
::$lazyOptions[$name], $this, $name );
131 if ( !empty( self
::$inCacheKey[$name] ) ) {
132 $this->optionUsed( $name );
134 return $this->options
[$name];
138 * Set an option, generically
140 * @param string $name Option name
141 * @param mixed $value New value. Passing null will set null, unlike many
142 * of the existing accessors which ignore null for historical reasons.
143 * @return mixed Old value
145 public function setOption( $name, $value ) {
146 if ( !array_key_exists( $name, $this->options
) ) {
147 throw new InvalidArgumentException( "Unknown parser option $name" );
149 $old = $this->options
[$name];
150 $this->options
[$name] = $value;
155 * Legacy implementation
156 * @since 1.30 For implementing legacy setters only. Don't use this in new code.
157 * @deprecated since 1.30
158 * @param string $name Option name
159 * @param mixed $value New value. Passing null does not set the value.
160 * @return mixed Old value
162 protected function setOptionLegacy( $name, $value ) {
163 if ( !array_key_exists( $name, $this->options
) ) {
164 throw new InvalidArgumentException( "Unknown parser option $name" );
166 return wfSetVar( $this->options
[$name], $value );
170 * Whether to extract interlanguage links
172 * When true, interlanguage links will be returned by
173 * ParserOutput::getLanguageLinks() instead of generating link HTML.
177 public function getInterwikiMagic() {
178 return $this->getOption( 'interwikiMagic' );
182 * Specify whether to extract interlanguage links
183 * @param bool|null $x New value (null is no change)
184 * @return bool Old value
186 public function setInterwikiMagic( $x ) {
187 return $this->setOptionLegacy( 'interwikiMagic', $x );
191 * Allow all external images inline?
194 public function getAllowExternalImages() {
195 return $this->getOption( 'allowExternalImages' );
199 * Allow all external images inline?
200 * @param bool|null $x New value (null is no change)
201 * @return bool Old value
203 public function setAllowExternalImages( $x ) {
204 return $this->setOptionLegacy( 'allowExternalImages', $x );
208 * External images to allow
210 * When self::getAllowExternalImages() is false
212 * @return string|string[] URLs to allow
214 public function getAllowExternalImagesFrom() {
215 return $this->getOption( 'allowExternalImagesFrom' );
219 * External images to allow
221 * When self::getAllowExternalImages() is false
223 * @param string|string[]|null $x New value (null is no change)
224 * @return string|string[] Old value
226 public function setAllowExternalImagesFrom( $x ) {
227 return $this->setOptionLegacy( 'allowExternalImagesFrom', $x );
231 * Use the on-wiki external image whitelist?
234 public function getEnableImageWhitelist() {
235 return $this->getOption( 'enableImageWhitelist' );
239 * Use the on-wiki external image whitelist?
240 * @param bool|null $x New value (null is no change)
241 * @return bool Old value
243 public function setEnableImageWhitelist( $x ) {
244 return $this->setOptionLegacy( 'enableImageWhitelist', $x );
248 * Automatically number headings?
251 public function getNumberHeadings() {
252 return $this->getOption( 'numberheadings' );
256 * Automatically number headings?
257 * @param bool|null $x New value (null is no change)
258 * @return bool Old value
260 public function setNumberHeadings( $x ) {
261 return $this->setOptionLegacy( 'numberheadings', $x );
265 * Allow inclusion of special pages?
268 public function getAllowSpecialInclusion() {
269 return $this->getOption( 'allowSpecialInclusion' );
273 * Allow inclusion of special pages?
274 * @param bool|null $x New value (null is no change)
275 * @return bool Old value
277 public function setAllowSpecialInclusion( $x ) {
278 return $this->setOptionLegacy( 'allowSpecialInclusion', $x );
282 * Use tidy to cleanup output HTML?
285 public function getTidy() {
286 return $this->getOption( 'tidy' );
290 * Use tidy to cleanup output HTML?
291 * @param bool|null $x New value (null is no change)
292 * @return bool Old value
294 public function setTidy( $x ) {
295 return $this->setOptionLegacy( 'tidy', $x );
299 * Parsing an interface message?
302 public function getInterfaceMessage() {
303 return $this->getOption( 'interfaceMessage' );
307 * Parsing an interface message?
308 * @param bool|null $x New value (null is no change)
309 * @return bool Old value
311 public function setInterfaceMessage( $x ) {
312 return $this->setOptionLegacy( 'interfaceMessage', $x );
316 * Target language for the parse
317 * @return Language|null
319 public function getTargetLanguage() {
320 return $this->getOption( 'targetLanguage' );
324 * Target language for the parse
325 * @param Language|null $x New value
326 * @return Language|null Old value
328 public function setTargetLanguage( $x ) {
329 return $this->setOption( 'targetLanguage', $x );
333 * Maximum size of template expansions, in bytes
336 public function getMaxIncludeSize() {
337 return $this->getOption( 'maxIncludeSize' );
341 * Maximum size of template expansions, in bytes
342 * @param int|null $x New value (null is no change)
343 * @return int Old value
345 public function setMaxIncludeSize( $x ) {
346 return $this->setOptionLegacy( 'maxIncludeSize', $x );
350 * Maximum number of nodes touched by PPFrame::expand()
353 public function getMaxPPNodeCount() {
354 return $this->getOption( 'maxPPNodeCount' );
358 * Maximum number of nodes touched by PPFrame::expand()
359 * @param int|null $x New value (null is no change)
360 * @return int Old value
362 public function setMaxPPNodeCount( $x ) {
363 return $this->setOptionLegacy( 'maxPPNodeCount', $x );
367 * Maximum number of nodes generated by Preprocessor::preprocessToObj()
370 public function getMaxGeneratedPPNodeCount() {
371 return $this->getOption( 'maxGeneratedPPNodeCount' );
375 * Maximum number of nodes generated by Preprocessor::preprocessToObj()
376 * @param int|null $x New value (null is no change)
379 public function setMaxGeneratedPPNodeCount( $x ) {
380 return $this->setOptionLegacy( 'maxGeneratedPPNodeCount', $x );
384 * Maximum recursion depth in PPFrame::expand()
387 public function getMaxPPExpandDepth() {
388 return $this->getOption( 'maxPPExpandDepth' );
392 * Maximum recursion depth for templates within templates
395 public function getMaxTemplateDepth() {
396 return $this->getOption( 'maxTemplateDepth' );
400 * Maximum recursion depth for templates within templates
401 * @param int|null $x New value (null is no change)
402 * @return int Old value
404 public function setMaxTemplateDepth( $x ) {
405 return $this->setOptionLegacy( 'maxTemplateDepth', $x );
409 * Maximum number of calls per parse to expensive parser functions
413 public function getExpensiveParserFunctionLimit() {
414 return $this->getOption( 'expensiveParserFunctionLimit' );
418 * Maximum number of calls per parse to expensive parser functions
420 * @param int|null $x New value (null is no change)
421 * @return int Old value
423 public function setExpensiveParserFunctionLimit( $x ) {
424 return $this->setOptionLegacy( 'expensiveParserFunctionLimit', $x );
428 * Remove HTML comments
429 * @warning Only applies to preprocess operations
432 public function getRemoveComments() {
433 return $this->getOption( 'removeComments' );
437 * Remove HTML comments
438 * @warning Only applies to preprocess operations
439 * @param bool|null $x New value (null is no change)
440 * @return bool Old value
442 public function setRemoveComments( $x ) {
443 return $this->setOptionLegacy( 'removeComments', $x );
447 * Enable limit report in an HTML comment on output
450 public function getEnableLimitReport() {
451 return $this->getOption( 'enableLimitReport' );
455 * Enable limit report in an HTML comment on output
456 * @param bool|null $x New value (null is no change)
457 * @return bool Old value
459 public function enableLimitReport( $x = true ) {
460 return $this->setOptionLegacy( 'enableLimitReport', $x );
464 * Clean up signature texts?
465 * @see Parser::cleanSig
468 public function getCleanSignatures() {
469 return $this->getOption( 'cleanSignatures' );
473 * Clean up signature texts?
474 * @see Parser::cleanSig
475 * @param bool|null $x New value (null is no change)
476 * @return bool Old value
478 public function setCleanSignatures( $x ) {
479 return $this->setOptionLegacy( 'cleanSignatures', $x );
483 * Target attribute for external links
486 public function getExternalLinkTarget() {
487 return $this->getOption( 'externalLinkTarget' );
491 * Target attribute for external links
492 * @param string|null $x New value (null is no change)
493 * @return string Old value
495 public function setExternalLinkTarget( $x ) {
496 return $this->setOptionLegacy( 'externalLinkTarget', $x );
500 * Whether content conversion should be disabled
503 public function getDisableContentConversion() {
504 return $this->getOption( 'disableContentConversion' );
508 * Whether content conversion should be disabled
509 * @param bool|null $x New value (null is no change)
510 * @return bool Old value
512 public function disableContentConversion( $x = true ) {
513 return $this->setOptionLegacy( 'disableContentConversion', $x );
517 * Whether title conversion should be disabled
520 public function getDisableTitleConversion() {
521 return $this->getOption( 'disableTitleConversion' );
525 * Whether title conversion should be disabled
526 * @param bool|null $x New value (null is no change)
527 * @return bool Old value
529 public function disableTitleConversion( $x = true ) {
530 return $this->setOptionLegacy( 'disableTitleConversion', $x );
534 * Thumb size preferred by the user.
537 public function getThumbSize() {
538 return $this->getOption( 'thumbsize' );
542 * Thumb size preferred by the user.
543 * @param int|null $x New value (null is no change)
544 * @return int Old value
546 public function setThumbSize( $x ) {
547 return $this->setOptionLegacy( 'thumbsize', $x );
551 * Thumb size preferred by the user.
554 public function getStubThreshold() {
555 return $this->getOption( 'stubthreshold' );
559 * Thumb size preferred by the user.
560 * @param int|null $x New value (null is no change)
561 * @return int Old value
563 public function setStubThreshold( $x ) {
564 return $this->setOptionLegacy( 'stubthreshold', $x );
568 * Parsing the page for a "preview" operation?
571 public function getIsPreview() {
572 return $this->getOption( 'isPreview' );
576 * Parsing the page for a "preview" operation?
577 * @param bool|null $x New value (null is no change)
578 * @return bool Old value
580 public function setIsPreview( $x ) {
581 return $this->setOptionLegacy( 'isPreview', $x );
585 * Parsing the page for a "preview" operation on a single section?
588 public function getIsSectionPreview() {
589 return $this->getOption( 'isSectionPreview' );
593 * Parsing the page for a "preview" operation on a single section?
594 * @param bool|null $x New value (null is no change)
595 * @return bool Old value
597 public function setIsSectionPreview( $x ) {
598 return $this->setOptionLegacy( 'isSectionPreview', $x );
602 * Parsing the printable version of the page?
605 public function getIsPrintable() {
606 return $this->getOption( 'printable' );
610 * Parsing the printable version of the page?
611 * @param bool|null $x New value (null is no change)
612 * @return bool Old value
614 public function setIsPrintable( $x ) {
615 return $this->setOptionLegacy( 'printable', $x );
619 * Transform wiki markup when saving the page?
622 public function getPreSaveTransform() {
623 return $this->getOption( 'preSaveTransform' );
627 * Transform wiki markup when saving the page?
628 * @param bool|null $x New value (null is no change)
629 * @return bool Old value
631 public function setPreSaveTransform( $x ) {
632 return $this->setOptionLegacy( 'preSaveTransform', $x );
639 public function getDateFormat() {
640 return $this->getOption( 'dateformat' );
644 * Lazy initializer for dateFormat
646 private static function initDateFormat( $popt ) {
647 return $popt->mUser
->getDatePreference();
652 * @param string|null $x New value (null is no change)
653 * @return string Old value
655 public function setDateFormat( $x ) {
656 return $this->setOptionLegacy( 'dateformat', $x );
660 * Get the user language used by the parser for this page and split the parser cache.
662 * @warning: Calling this causes the parser cache to be fragmented by user language!
663 * To avoid cache fragmentation, output should not depend on the user language.
664 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
666 * @note This function will trigger a cache fragmentation by recording the
667 * 'userlang' option, see optionUsed(). This is done to avoid cache pollution
668 * when the page is rendered based on the language of the user.
670 * @note When saving, this will return the default language instead of the user's.
671 * {{int: }} uses this which used to produce inconsistent link tables (T16404).
676 public function getUserLangObj() {
677 return $this->getOption( 'userlang' );
681 * Same as getUserLangObj() but returns a string instead.
683 * @warning: Calling this causes the parser cache to be fragmented by user language!
684 * To avoid cache fragmentation, output should not depend on the user language.
685 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
687 * @see getUserLangObj()
689 * @return string Language code
692 public function getUserLang() {
693 return $this->getUserLangObj()->getCode();
697 * Set the user language used by the parser for this page and split the parser cache.
698 * @param string|Language $x New value
699 * @return Language Old value
701 public function setUserLang( $x ) {
702 if ( is_string( $x ) ) {
703 $x = Language
::factory( $x );
706 return $this->setOptionLegacy( 'userlang', $x );
710 * Are magic ISBN links enabled?
714 public function getMagicISBNLinks() {
715 return $this->getOption( 'magicISBNLinks' );
719 * Are magic PMID links enabled?
723 public function getMagicPMIDLinks() {
724 return $this->getOption( 'magicPMIDLinks' );
727 * Are magic RFC links enabled?
731 public function getMagicRFCLinks() {
732 return $this->getOption( 'magicRFCLinks' );
736 * If the wiki is configured to allow raw html ($wgRawHtml = true)
737 * is it allowed in the specific case of parsing this page.
739 * This is meant to disable unsafe parser tags in cases where
740 * a malicious user may control the input to the parser.
742 * @note This is expected to be true for normal pages even if the
743 * wiki has $wgRawHtml disabled in general. The setting only
744 * signifies that raw html would be unsafe in the current context
745 * provided that raw html is allowed at all.
749 public function getAllowUnsafeRawHtml() {
750 return $this->getOption( 'allowUnsafeRawHtml' );
754 * If the wiki is configured to allow raw html ($wgRawHtml = true)
755 * is it allowed in the specific case of parsing this page.
756 * @see self::getAllowUnsafeRawHtml()
758 * @param bool|null $x Value to set or null to get current value
759 * @return bool Current value for allowUnsafeRawHtml
761 public function setAllowUnsafeRawHtml( $x ) {
762 return $this->setOptionLegacy( 'allowUnsafeRawHtml', $x );
766 * Class to use to wrap output from Parser::parse()
768 * @return string|bool
770 public function getWrapOutputClass() {
771 return $this->getOption( 'wrapclass' );
775 * CSS class to use to wrap output from Parser::parse()
777 * @param string $className Class name to use for wrapping.
778 * Passing false to indicate "no wrapping" was deprecated in MediaWiki 1.31.
779 * @return string|bool Current value
781 public function setWrapOutputClass( $className ) {
782 if ( $className === true ) { // DWIM, they probably want the default class name
783 $className = 'mw-parser-output';
785 if ( $className === false ) {
786 wfDeprecated( __METHOD__
. '( false )', '1.31' );
788 return $this->setOption( 'wrapclass', $className );
792 * Callback for current revision fetching; first argument to call_user_func().
796 public function getCurrentRevisionCallback() {
797 return $this->getOption( 'currentRevisionCallback' );
801 * Callback for current revision fetching; first argument to call_user_func().
803 * @param callable|null $x New value (null is no change)
804 * @return callable Old value
806 public function setCurrentRevisionCallback( $x ) {
807 return $this->setOptionLegacy( 'currentRevisionCallback', $x );
811 * Callback for template fetching; first argument to call_user_func().
814 public function getTemplateCallback() {
815 return $this->getOption( 'templateCallback' );
819 * Callback for template fetching; first argument to call_user_func().
820 * @param callable|null $x New value (null is no change)
821 * @return callable Old value
823 public function setTemplateCallback( $x ) {
824 return $this->setOptionLegacy( 'templateCallback', $x );
828 * Callback to generate a guess for {{REVISIONID}}
830 * @return callable|null
832 public function getSpeculativeRevIdCallback() {
833 return $this->getOption( 'speculativeRevIdCallback' );
837 * Callback to generate a guess for {{REVISIONID}}
839 * @param callable|null $x New value (null is no change)
840 * @return callable|null Old value
842 public function setSpeculativeRevIdCallback( $x ) {
843 return $this->setOptionLegacy( 'speculativeRevIdCallback', $x );
849 * Timestamp used for {{CURRENTDAY}} etc.
852 public function getTimestamp() {
853 if ( !isset( $this->mTimestamp
) ) {
854 $this->mTimestamp
= wfTimestampNow();
856 return $this->mTimestamp
;
860 * Timestamp used for {{CURRENTDAY}} etc.
861 * @param string|null $x New value (null is no change)
862 * @return string Old value
864 public function setTimestamp( $x ) {
865 return wfSetVar( $this->mTimestamp
, $x );
869 * Create "edit section" links?
870 * @deprecated since 1.31, use ParserOutput::getText() options instead.
873 public function getEditSection() {
874 wfDeprecated( __METHOD__
, '1.31' );
879 * Create "edit section" links?
880 * @deprecated since 1.31, use ParserOutput::getText() options instead.
881 * @param bool|null $x New value (null is no change)
882 * @return bool Old value
884 public function setEditSection( $x ) {
885 wfDeprecated( __METHOD__
, '1.31' );
890 * Set the redirect target.
892 * Note that setting or changing this does not *make* the page a redirect
893 * or change its target, it merely records the information for reference
897 * @param Title|null $title
899 function setRedirectTarget( $title ) {
900 $this->redirectTarget
= $title;
904 * Get the previously-set redirect target.
909 function getRedirectTarget() {
910 return $this->redirectTarget
;
914 * Extra key that should be present in the parser cache key.
915 * @warning Consider registering your additional options with the
916 * ParserOptionsRegister hook instead of using this method.
919 public function addExtraKey( $key ) {
920 $this->mExtraKey
.= '!' . $key;
927 public function getUser() {
932 * @warning For interaction with the parser cache, use
933 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
934 * ParserOptions::newCanonical() instead.
936 * @param Language $lang
938 public function __construct( $user = null, $lang = null ) {
939 if ( $user === null ) {
941 if ( $wgUser === null ) {
947 if ( $lang === null ) {
949 if ( !StubObject
::isRealObject( $wgLang ) ) {
954 $this->initialiseFromUser( $user, $lang );
958 * Get a ParserOptions object for an anonymous user
959 * @warning For interaction with the parser cache, use
960 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
961 * ParserOptions::newCanonical() instead.
963 * @return ParserOptions
965 public static function newFromAnon() {
967 return new ParserOptions( new User
, $wgContLang );
971 * Get a ParserOptions object from a given user.
972 * Language will be taken from $wgLang.
974 * @warning For interaction with the parser cache, use
975 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
976 * ParserOptions::newCanonical() instead.
978 * @return ParserOptions
980 public static function newFromUser( $user ) {
981 return new ParserOptions( $user );
985 * Get a ParserOptions object from a given user and language
987 * @warning For interaction with the parser cache, use
988 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
989 * ParserOptions::newCanonical() instead.
991 * @param Language $lang
992 * @return ParserOptions
994 public static function newFromUserAndLang( User
$user, Language
$lang ) {
995 return new ParserOptions( $user, $lang );
999 * Get a ParserOptions object from a IContextSource object
1001 * @warning For interaction with the parser cache, use
1002 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
1003 * ParserOptions::newCanonical() instead.
1004 * @param IContextSource $context
1005 * @return ParserOptions
1007 public static function newFromContext( IContextSource
$context ) {
1008 return new ParserOptions( $context->getUser(), $context->getLanguage() );
1012 * Creates a "canonical" ParserOptions object
1014 * For historical reasons, certain options have default values that are
1015 * different from the canonical values used for caching.
1018 * @param User|null $user
1019 * @param Language|StubObject|null $lang
1020 * @return ParserOptions
1022 public static function newCanonical( User
$user = null, $lang = null ) {
1023 $ret = new ParserOptions( $user, $lang );
1024 foreach ( self
::getCanonicalOverrides() as $k => $v ) {
1025 $ret->setOption( $k, $v );
1031 * Get default option values
1032 * @warning If you change the default for an existing option (unless it's
1033 * being overridden by self::getCanonicalOverrides()), all existing parser
1034 * cache entries will be invalid. To avoid bugs, you'll need to handle
1035 * that somehow (e.g. with the RejectParserCacheValue hook) because
1036 * MediaWiki won't do it for you.
1039 private static function getDefaults() {
1040 global $wgInterwikiMagic, $wgAllowExternalImages,
1041 $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
1042 $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
1043 $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
1044 $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion,
1045 $wgEnableMagicLinks, $wgContLang;
1047 if ( self
::$defaults === null ) {
1048 // *UPDATE* ParserOptions::matches() if any of this changes as needed
1050 'dateformat' => null,
1052 'interfaceMessage' => false,
1053 'targetLanguage' => null,
1054 'removeComments' => true,
1055 'enableLimitReport' => false,
1056 'preSaveTransform' => true,
1057 'isPreview' => false,
1058 'isSectionPreview' => false,
1059 'printable' => false,
1060 'allowUnsafeRawHtml' => true,
1061 'wrapclass' => 'mw-parser-output',
1062 'currentRevisionCallback' => [ Parser
::class, 'statelessFetchRevision' ],
1063 'templateCallback' => [ Parser
::class, 'statelessFetchTemplate' ],
1064 'speculativeRevIdCallback' => null,
1067 Hooks
::run( 'ParserOptionsRegister', [
1070 &self
::$lazyOptions,
1073 ksort( self
::$inCacheKey );
1076 // Unit tests depend on being able to modify the globals at will
1077 return self
::$defaults +
[
1078 'interwikiMagic' => $wgInterwikiMagic,
1079 'allowExternalImages' => $wgAllowExternalImages,
1080 'allowExternalImagesFrom' => $wgAllowExternalImagesFrom,
1081 'enableImageWhitelist' => $wgEnableImageWhitelist,
1082 'allowSpecialInclusion' => $wgAllowSpecialInclusion,
1083 'maxIncludeSize' => $wgMaxArticleSize * 1024,
1084 'maxPPNodeCount' => $wgMaxPPNodeCount,
1085 'maxGeneratedPPNodeCount' => $wgMaxGeneratedPPNodeCount,
1086 'maxPPExpandDepth' => $wgMaxPPExpandDepth,
1087 'maxTemplateDepth' => $wgMaxTemplateDepth,
1088 'expensiveParserFunctionLimit' => $wgExpensiveParserFunctionLimit,
1089 'externalLinkTarget' => $wgExternalLinkTarget,
1090 'cleanSignatures' => $wgCleanSignatures,
1091 'disableContentConversion' => $wgDisableLangConversion,
1092 'disableTitleConversion' => $wgDisableLangConversion ||
$wgDisableTitleConversion,
1093 'magicISBNLinks' => $wgEnableMagicLinks['ISBN'],
1094 'magicPMIDLinks' => $wgEnableMagicLinks['PMID'],
1095 'magicRFCLinks' => $wgEnableMagicLinks['RFC'],
1096 'numberheadings' => User
::getDefaultOption( 'numberheadings' ),
1097 'thumbsize' => User
::getDefaultOption( 'thumbsize' ),
1098 'stubthreshold' => 0,
1099 'userlang' => $wgContLang,
1104 * Get "canonical" non-default option values
1105 * @see self::newCanonical
1106 * @warning If you change the override for an existing option, all existing
1107 * parser cache entries will be invalid. To avoid bugs, you'll need to
1108 * handle that somehow (e.g. with the RejectParserCacheValue hook) because
1109 * MediaWiki won't do it for you.
1112 private static function getCanonicalOverrides() {
1113 global $wgEnableParserLimitReporting;
1117 'enableLimitReport' => $wgEnableParserLimitReporting,
1125 * @param Language $lang
1127 private function initialiseFromUser( $user, $lang ) {
1128 $this->options
= self
::getDefaults();
1130 $this->mUser
= $user;
1131 $this->options
['numberheadings'] = $user->getOption( 'numberheadings' );
1132 $this->options
['thumbsize'] = $user->getOption( 'thumbsize' );
1133 $this->options
['stubthreshold'] = $user->getStubThreshold();
1134 $this->options
['userlang'] = $lang;
1138 * Check if these options match that of another options set
1140 * This ignores report limit settings that only affect HTML comments
1142 * @param ParserOptions $other
1146 public function matches( ParserOptions
$other ) {
1147 // Populate lazy options
1148 foreach ( self
::$lazyOptions as $name => $callback ) {
1149 if ( $this->options
[$name] === null ) {
1150 $this->options
[$name] = call_user_func( $callback, $this, $name );
1152 if ( $other->options
[$name] === null ) {
1153 $other->options
[$name] = call_user_func( $callback, $other, $name );
1157 // Compare most options
1158 $options = array_keys( $this->options
);
1159 $options = array_diff( $options, [
1160 'enableLimitReport', // only affects HTML comments
1162 foreach ( $options as $option ) {
1163 $o1 = $this->optionToString( $this->options
[$option] );
1164 $o2 = $this->optionToString( $other->options
[$option] );
1165 if ( $o1 !== $o2 ) {
1170 // Compare most other fields
1171 $fields = array_keys( get_class_vars( __CLASS__
) );
1172 $fields = array_diff( $fields, [
1173 'defaults', // static
1174 'lazyOptions', // static
1175 'inCacheKey', // static
1176 'options', // Already checked above
1177 'onAccessCallback', // only used for ParserOutput option tracking
1179 foreach ( $fields as $field ) {
1180 if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) {
1189 * Registers a callback for tracking which ParserOptions which are used.
1190 * This is a private API with the parser.
1191 * @param callable $callback
1193 public function registerWatcher( $callback ) {
1194 $this->onAccessCallback
= $callback;
1198 * Called when an option is accessed.
1199 * Calls the watcher that was set using registerWatcher().
1200 * Typically, the watcher callback is ParserOutput::registerOption().
1201 * The information registered that way will be used by ParserCache::save().
1203 * @param string $optionName Name of the option
1205 public function optionUsed( $optionName ) {
1206 if ( $this->onAccessCallback
) {
1207 call_user_func( $this->onAccessCallback
, $optionName );
1212 * Returns the full array of options that would have been used by
1214 * Used to get the old parser cache entries when available.
1215 * @deprecated since 1.30. You probably want self::allCacheVaryingOptions() instead.
1218 public static function legacyOptions() {
1219 wfDeprecated( __METHOD__
, '1.30' );
1231 * Return all option keys that vary the options hash
1235 public static function allCacheVaryingOptions() {
1236 // Trigger a call to the 'ParserOptionsRegister' hook if it hasn't
1237 // already been called.
1238 if ( self
::$defaults === null ) {
1239 self
::getDefaults();
1241 return array_keys( array_filter( self
::$inCacheKey ) );
1245 * Convert an option to a string value
1246 * @param mixed $value
1249 private function optionToString( $value ) {
1250 if ( $value === true ) {
1252 } elseif ( $value === false ) {
1254 } elseif ( $value === null ) {
1256 } elseif ( $value instanceof Language
) {
1257 return $value->getCode();
1258 } elseif ( is_array( $value ) ) {
1259 return '[' . implode( ',', array_map( [ $this, 'optionToString' ], $value ) ) . ']';
1261 return (string)$value;
1266 * Generate a hash string with the values set on these ParserOptions
1267 * for the keys given in the array.
1268 * This will be used as part of the hash key for the parser cache,
1269 * so users sharing the options with vary for the same page share
1270 * the same cached data safely.
1273 * @param string[] $forOptions
1274 * @param Title $title Used to get the content language of the page (since r97636)
1275 * @return string Page rendering hash
1277 public function optionsHash( $forOptions, $title = null ) {
1278 global $wgRenderHashAppend;
1280 $inCacheKey = self
::allCacheVaryingOptions();
1282 // Resolve any lazy options
1283 foreach ( array_intersect( $forOptions, $inCacheKey, array_keys( self
::$lazyOptions ) ) as $k ) {
1284 if ( $this->options
[$k] === null ) {
1285 $this->options
[$k] = call_user_func( self
::$lazyOptions[$k], $this, $k );
1289 $options = $this->options
;
1290 $defaults = self
::getCanonicalOverrides() + self
::getDefaults();
1292 // We only include used options with non-canonical values in the key
1293 // so adding a new option doesn't invalidate the entire parser cache.
1294 // The drawback to this is that changing the default value of an option
1295 // requires manual invalidation of existing cache entries, as mentioned
1296 // in the docs on the relevant methods and hooks.
1298 foreach ( array_intersect( $inCacheKey, $forOptions ) as $option ) {
1299 $v = $this->optionToString( $options[$option] );
1300 $d = $this->optionToString( $defaults[$option] );
1302 $values[] = "$option=$v";
1306 $confstr = $values ?
implode( '!', $values ) : 'canonical';
1308 // add in language specific options, if any
1309 // @todo FIXME: This is just a way of retrieving the url/user preferred variant
1310 if ( !is_null( $title ) ) {
1311 $confstr .= $title->getPageLanguage()->getExtraHashOptions();
1314 $confstr .= $wgContLang->getExtraHashOptions();
1317 $confstr .= $wgRenderHashAppend;
1319 if ( $this->mExtraKey
!= '' ) {
1320 $confstr .= $this->mExtraKey
;
1323 // Give a chance for extensions to modify the hash, if they have
1324 // extra options or other effects on the parser cache.
1325 Hooks
::run( 'PageRenderingHash', [ &$confstr, $this->getUser(), &$forOptions ] );
1327 // Make it a valid memcached key fragment
1328 $confstr = str_replace( ' ', '_', $confstr );
1334 * Test whether these options are safe to cache
1338 public function isSafeToCache() {
1339 $defaults = self
::getCanonicalOverrides() + self
::getDefaults();
1340 foreach ( $this->options
as $option => $value ) {
1341 if ( empty( self
::$inCacheKey[$option] ) ) {
1342 $v = $this->optionToString( $value );
1343 $d = $this->optionToString( $defaults[$option] );
1353 * Sets a hook to force that a page exists, and sets a current revision callback to return
1354 * a revision with custom content when the current revision of the page is requested.
1357 * @param Title $title
1358 * @param Content $content
1359 * @param User $user The user that the fake revision is attributed to
1360 * @return ScopedCallback to unset the hook
1362 public function setupFakeRevision( $title, $content, $user ) {
1363 $oldCallback = $this->setCurrentRevisionCallback(
1365 $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback
1367 if ( $titleToCheck->equals( $title ) ) {
1368 return new Revision( [
1369 'page' => $title->getArticleID(),
1370 'user_text' => $user->getName(),
1371 'user' => $user->getId(),
1372 'parent_id' => $title->getLatestRevID(),
1374 'content' => $content
1377 return call_user_func( $oldCallback, $titleToCheck, $parser );
1383 $wgHooks['TitleExists'][] =
1384 function ( $titleToCheck, &$exists ) use ( $title ) {
1385 if ( $titleToCheck->equals( $title ) ) {
1389 end( $wgHooks['TitleExists'] );
1390 $key = key( $wgHooks['TitleExists'] );
1391 $linkCache = MediaWikiServices
::getInstance()->getLinkCache();
1392 $linkCache->clearBadLink( $title->getPrefixedDBkey() );
1393 return new ScopedCallback( function () use ( $title, $key, $linkCache ) {
1395 unset( $wgHooks['TitleExists'][$key] );
1396 $linkCache->clearLink( $title );
1402 * For really cool vim folding this needs to be at the end:
1403 * vim: foldmarker=@{,@} foldmethod=marker